/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;

namespace Cobalt.Framework
{

	public class TextBuffer
	{
		#region Buffer Management

		private char[] buffer;
		private int length;

		public TextBuffer()
			: this( 1024 )
		{
		}

		public TextBuffer( int capacity )
		{
			if( capacity < 0 )
				throw new ArgumentOutOfRangeException( "capacity" );

			Array.Resize( ref buffer, capacity );
		}

		public char[] Buffer
		{
			get { return buffer; }
		}

		public int Capacity
		{
			get { return buffer.Length; }
			set { Resize( value ); }
		}

		public unsafe int Length
		{
			get { return length; }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException( "Length" );

				if( value < length )
				{
					Array.Clear( buffer, value, length - value );
				}
				else if( value > buffer.Length )
					Resize( value );

				length = value;
			}
		}

		public void Clear()
		{
			Length = 0;
		}

		protected unsafe void Resize( int newCapacity )
		{
			if( newCapacity < 0 )
				throw new ArgumentOutOfRangeException( "newCapacity" );

			Array.Resize( ref buffer, newCapacity );
		}

		public void EnsureCapacity( int minCapacity )
		{
			if( minCapacity < 0 )
				throw new ArgumentOutOfRangeException( "minCapacity" );

			if( buffer.Length < minCapacity )
				Resize( minCapacity );
		}

		public override string ToString()
		{
			return new string( buffer, 0, length );
		}

		#endregion

		#region Editing Methods

		protected unsafe void AppendBuf( char* buf, int count )
		{
			if( buf == null )
				throw new ArgumentNullException( "buf" );

			if( count < 0 )
				throw new ArgumentOutOfRangeException( "count" );

			EnsureCapacity( length + count );

			fixed( char* pBuf = buffer )
			{
				for( int i = 0; i < count; i++ )
					pBuf[length++] = buf[i];
			}
		}

		protected unsafe void ReverseAppendBuf( char* buf, int count )
		{
			if( buf == null )
				throw new ArgumentNullException( "buf" );

			if( count < 0 )
				throw new ArgumentOutOfRangeException( "count" );

			EnsureCapacity( length + count );

			fixed( char* pBuf = buffer )
			{
				while( count-- != 0 )
					pBuf[length++] = buf[count];
			}
		}

		public void AppendChars( char ch, int count )
		{
			if( count < 0 )
				throw new ArgumentOutOfRangeException( "count" );

			EnsureCapacity( length + count );
			buffer[length++] = ch;
		}

		public unsafe void AppendText( string text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			if( count < 0 || index < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			fixed( char* pText = text )
				AppendBuf( pText + index, count );
		}

		public unsafe void AppendText( string text )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			AppendText( text, 0, text.Length );
		}

		public unsafe void AppendText( char[] text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			if( count < 0 || index < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			fixed( char* pText = text )
				AppendBuf( pText + index, count );
		}

		public unsafe void AppendText( char[] text )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			AppendText( text, 0, text.Length );
		}

		public unsafe void AppendText( TextBuffer text, int index, int count )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			if( count < 0 || index < 0 || index + count > text.Length )
				throw new ArgumentOutOfRangeException();

			fixed( char* pText = text.buffer )
				AppendBuf( pText + index, count );
		}

		public unsafe void AppendText( TextBuffer text )
		{
			if( text == null )
				throw new ArgumentNullException( "text" );

			AppendText( text, 0, text.Length );
		}

		public void AppendInt( int value, int minWidth, char padChar )
		{
			bool neg = value < 0;
			if( neg )
				value = -value;

			int width = neg ? 1 : 0;
			if( value != 0 )
			{
				for( int v = value; v != 0; v /= 10 )
					width++;
			}
			else
			{
				width = 1;
			}

			EnsureCapacity( length + Math.Max( width, minWidth ) );

			for( int i = width; i < minWidth; i++ )
				buffer[length++] = padChar;

			if( neg )
			{
				buffer[length++] = '-';
				width--;
			}

			int l = length + width;
			length = l;

			do
			{
				buffer[--l] = (char)('0' + value % 10);
				value /= 10;
			} while( value != 0 );
		}

		/// <summary>
		/// Appends a floating point number to the string.
		/// </summary>
		/// <param name="value">The number to print to the buffer.</param>
		/// <param name="minWidth">The minimum width of the whole number part.</param>
		/// <param name="padChar">The character to use to pad out the whole number part.</param>
		/// <param name="precision">The number of digits to emit after the decimal.</param>
		public void AppendFloat( float value, int minWidth, char padChar, int precision )
		{
			if( value < 0 )
			{
				EnsureCapacity( length + 1 );

				buffer[length++] = '-';
				value = -value;
			}

			AppendInt( (int)value, minWidth, padChar );

			if( precision == 0 )
				return;

			//append the fractional part

			AppendChars( '.', 1 );

			if( precision < 0 )
				precision = 6;

			if( value < 0 )
				value = -value;
			value -= (int)value;

			for( int i = 0; i < precision; i++ )
				value *= 10;

			AppendInt( (int)value, precision, '0' );
		}

		#endregion
	}

}